Move Where onto Periscope; retire LogKit + LogViewerUI#94
Open
kyleve wants to merge 12 commits into
Open
Conversation
Rebuild RegionLog as a Periscope facade: one "RegionKit" root scope with a typed LogEvent per collaborator (RegionAttributor / RegionCatalog / RegionGeometryCatalog), emitting into the process-wide Periscope.shared system instead of a private LogKit LogStore. Errors ride as LogAttachments and region ids as externalIDs. Swap RegionKit's LogKit dependency for PeriscopeCore and add PeriscopeCore/UI/Tools to WhereUI (RegionMapView's one RegionLog call site moves over now to keep the tree green). Closes plan step: Migrate RegionKit.
Rebuild WhereLog as a Periscope facade rooted at a "Where" scope with grouping scopes (location, reminders, backup, widgets, session, evidence, recentActivity) and derive a typed LogEvent per collaborator. Convert every WhereCore call site to structured events, add measure() spans for store open, backup archive read/write, and recent-activity generation, attach errors via LogAttachment.error, and stamp day/sample/evidence externalIDs. Drop the dead FoundationModelSummaryGenerator logger and the unused appDelegate category. The legacy LogKit-backed WhereLog API (subsystem/store/Category/channel) is kept alongside the new tree so WhereUI and the extensions stay green until their own migration commits; WhereCore gains a PeriscopeCore dependency. Closes plan step: Migrate WhereCore.
Convert every WhereUI view-model/view logging site to typed Periscope LogEvents under the right scope: WhereModel/WhereLaunch/LoggedDaysModel as "Where" leaves; WhereSession/YearReport/Backup/Resolve/CalendarView under the session group; the evidence models under the evidence group; RecentActivityModel under recentActivity. Add a per-collaborator LogEvent file for each, drop the unused RemindersSettingsModel logger, and point DeveloperToolsView's legacy LogViewer at the single WhereLog buffer (RegionKit no longer feeds a LogViewerUI store) pending the PeriscopeTools rework. Closes plan step: Migrate WhereUI view models.
Wire the Where app's durable logging at process launch: open one on-disk PeriscopeStore, attach it to Periscope.shared as the durable sink, start the built-in ambient sources, and prune history past a two-week retention window. Runs off the launch critical path (the OSLog sink covers the pre-attach window), and hands the opened store to WhereModel so the DEBUG developer surface can browse persisted history. Closes the store-bootstrap plan step.
Rework the Where app's developer tools onto Periscope's UI: - DeveloperToolsView pushes PeriscopeViewer over the process-global store and adds an OpenSpansView monitor, replacing the LogViewerUI viewer. - RootView builds a PeriscopeInspector once the launch bootstrap opens the store, injects it via .periscopeInspector, and adds a "Log View Mode" toggle bound to it; debugLogInspectable(_:) badges are sprinkled on the evidence rows and the location status row (no-ops in release). - A DEBUG PeriscopeAlerter (threshold .warning) feeds an in-app toast surface (DeveloperToastCenter/Overlay) mounted above the app. - RootView seeds the root \.logContext so view-level freeform logging lands under the "Where" scope. - New DEBUG-only Strings for the open-spans and Log View Mode rows; hosting + Strings tests updated. Closes the dev-ui plan step.
Convert the WhereIntents, WhereShareExtension, and WhereWidgets logging call sites from the legacy LogKit facade (WhereLog.channel(_:)) to typed Periscope LogEvents emitted on WhereLog.root(_:) leaf scopes. Each extension runs in its own process, so Periscope.shared stays OSLog-only (no persistent store). Adds one LogEvent enum per extension (WhereIntentsLog, ShareExtensionLog, WhereWidgetsLog) carrying the fields the old messages interpolated, with error attachments on catch paths. Swaps the LogKit dependency for PeriscopeCore on WhereIntents (Package.swift) and WhereWidgets / WhereShareExtension (Project.swift). Closes the extensions plan step.
Every consumer now emits through Periscope, so retire the legacy logging stack entirely. Drops the LogKit-backed facade from WhereLog (the subsystem/store/Category/channel API and its tests), removes the LogKit / LogViewerUI products, targets, test bundles, and schemes from Package.swift and Project.swift, unlists them from the Where app and RegionViewer deps and the Stuff-iOS-Tests scheme, and deletes Shared/LogKit and Shared/LogViewerUI. Verified: project regenerates, ./swiftformat --lint clean, WhereCoreTests green, and the full Stuff-iOS-Tests scheme builds and links. Closes the remove-logkit plan step.
Rewrites the Where feature logging section (WhereLog as a Periscope scope tree, typed LogEvent leaves, one PeriscopeStore sink, OSLog-only extensions), swaps the LogKit dependency mentions for PeriscopeCore in the extension + RegionViewer + WhereIntents docs, and refreshes the root double-link note and never-swallow-errors rule for Periscope. Appends the rough edges hit while dogfooding the API: late-attached sinks don't backfill the pre-attach recent buffer, inspect-by-object is scope- not instance-granular, and there's no eager (non-async) store handle. Regenerates the gitignored CLAUDE.md mirrors via ./sync-agents. Closes the docs-dogfood plan step.
Records the post-#94/#96 follow-up to replace ad-hoc bare-string Periscope LogEvent externalIDs with the app's store:// object identities (one namespaced key across DB rows, backups, and logs). Notes the seams: move StoreURL to a module RegionKit can see, define store:// identities for the remaining object families, and pick synthetic identities for year/scope correlations. Deferred out of #94 to avoid coupling two open PRs.
…to-periscope # Conflicts: # Where/WhereUI/Sources/Resolution/ResolveModel.swift
Now that the store:// scheme (#96) is on main, key every store-object LogEvent externalID on the object's canonical store:// identity so the log tooling's inspect-by-object shares the same key the store and backups use — a namespaced, collision-free replacement for the ad-hoc bare strings (a year "2025" and an evidence UUID can no longer clash in one flat index). - Adds WhereStoreID (WhereCore, beside StoreURL): store://days/<iso>, store://years/<n>, store://evidence/<uuid>, store://samples/<uuid>, built via StoreURL, mirroring how DataIssueID vends store://issues/…. - Points the WhereCore (DayJournal, WidgetSnapshotPublisher, LocationIngestor, BackupService) and WhereUI (AddEvidence, EvidenceDetail, EvidenceList, LoggedDays, YearReport) event externalIDs at it; dismissals already ride DataIssueID.storeURL after the main merge. - RegionKit stays the deliberate exception: it's below the app's store:// convention and its regions are a bundled catalog, not store rows, so RegionAttributorLog keeps its bare catalog-id externalID. - Adds WhereStoreIDTests pinning the exact URL strings + StoreURL round-trip; updates WhereLogTests' externalID assertions; refreshes the Where/WhereCore AGENTS logging notes and drops the now-done TODO. Verified: ./swiftformat --lint clean and the full Stuff-iOS-Tests scheme green.
Adds RegionURL, RegionKit's local analog of WhereCore's StoreURL: a builder/parser for region://<collection>/<type>?<params> URLs, so a RegionKit value can vend a stable, namespaced URL identity without reaching up into app code for the store:// scheme. Region gains a regionURL (region://regions/<id>) mirroring how DataIssueID vends storeURL, distinct from Region's bare-rawValue storage Codable. RegionAttributorLog now carries a typed Region and keys its externalID on region.regionURL.absoluteString, so inspect-by-object works for regions too — a separate, intentionally parallel namespace to store:// (regions are a bundled catalog, not store rows). Adds RegionURLTests (builder round-trip, foreign-scheme rejection, and the Region identity), updates RegionLogTests for the typed payload, and refreshes the Where / RegionKit AGENTS logging notes. Verified: ./swiftformat --lint clean and the full Stuff-iOS-Tests scheme green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Migrates the Where app and every one of its modules from the old LogKit / LogViewerUI logging stack onto Periscope, then removes LogKit and LogViewerUI entirely. This is the full structured redesign (plan option B): every logging call site is now a typed
LogEventon aLog<Event>scope tree, a single process-globalPeriscopeStorereplaces the two oldLogStorering buffers, and the DEBUG developer surface adopts the PeriscopeTools UI.Landed as one commit per plan step, keeping the tree green at each step (LogKit survived until its last consumer moved over).
Logging design
WhereLogfacade vends a"Where"rootLogwith grouping scopes (location,reminders,backup,widgets,session,evidence,recentActivity); each collaborator derives its own typedLogEventleaf (WhereLog.<group>(SomeLog.self)/WhereLog.root(SomeLog.self)). RegionKit keeps its own"RegionKit"root scope but emits into the samePeriscope.shared.LogEventenum per collaborator, carrying the fields the old messages interpolated; catch-path events attachLogAttachment.error(_:). Severity conventions (info/warning/error/fault) and the PII-free.publiccontract are preserved; hot paths stay quiet.externalIDwith the object's canonical URL identity, so the log tooling's inspect-by-object shares the same key the store/backups use — a namespaced, collision-free replacement for ad-hoc bare strings. WhereCore/WhereUI events usestore://(DataIssueID.storeURLfor dismissals; a newWhereStoreIDvendsstore://days/…,store://years/…,store://evidence/…,store://samples/…). RegionKit can't see the app'sstore://types, so it owns a parallelregion://scheme (RegionURL+Region.regionURL→region://regions/<id>, modeled onStoreURL) andRegionAttributorLogkeys on that — a separate namespace, since regions are a bundled catalog, not store rows.PeriscopeStoreis attached toPeriscope.sharedat launch (AppDelegate→WhereLaunch.bootstrapLogging), with retention pruning, exposed to the UI via an optionalWhereModel.logStore. Durable logging is intentionally active in release and on background/headless launches too; widgets / share extension / intents run in their own processes and stay OSLog-only.PeriscopeViewerreplaces the old two-bufferLogViewer; adds anOpenSpansView, a Log View Mode toggle bound to aPeriscopeInspector,.logInspectable(_:)on high-value rows,.logContext(_:)at the root, and aPeriscopeAlerter(threshold.warning) wired to an in-app toast handler.Build & module wiring
Package.swift/Project.swift: dropped the LogKit + LogViewerUI products, targets, test bundles, and schemes; swapped their deps forPeriscopeCore(RegionKit, WhereCore, WhereIntents, WhereWidgets, WhereShareExtension) andPeriscopeCore/PeriscopeUI/PeriscopeToolson WhereUI; unlisted them from theWhereapp,RegionViewer, and theStuff-iOS-Testsscheme.Shared/LogKit/andShared/LogViewerUI/.WhereUITests/WhereIntentsTestsget them transitively and keep them out ofextraPackageProducts.Docs & dogfooding
AGENTS.md/README.mdlogging and dependency sections; regenerated the gitignoredCLAUDE.mdmirrors via./sync-agents.Shared/Periscope/TODOs.md: late-attached sinks don't backfill the pre-attach recent buffer; inspect-by-object is scope- not instance-granular; and there's no eager (non-async) store handle. (TheLogContextProvidingparent-hierarchy, multi-process/App Group store sharing, and PeriscopeTools-on-Broadway gaps were already tracked.)Testing
./swiftformat --lint— clean.tuist test Stuff-iOS-Tests(the full multi-bundle scheme, where the double-link regression reproduces) — all green.WhereLogTestsagainst the new scope tree / event payloads, addedWhereStoreIDTestsandRegionURLTestspinning the exact identity strings, and updatedScreenHostingTests(LogViewer→PeriscopeViewer, inspector wiring). The double-link regression guardWhereStylesheetTests.resolvesTraitAwareTokensFromTheBroadwayRootpasses.Notes
main(merges thestore://scheme from Clean up legacy Codable/field workarounds; key dismissals by store:// URL #96 and theSwiftDataStore.performfix from Fix SwiftDataStore.perform actor-reentrancy crash #97). Thestore://externalIDcanonicalization that was originally deferred is now done in this PR since Clean up legacy Codable/field workarounds; key dismissals by store:// URL #96 landed.Review notes
A code-review pass raised five items; outcomes:
bootstrapLoggingfailure path / retention prune — left as-is (the prune mechanism is covered at the Periscope layer; the remainder is process-global glue not worth an injection seam).externalIDtest coverage — done as part of thestore://canonicalization above (WhereStoreIDTests+ updatedWhereLogTests)..warning— kept as-is.configureDeveloperLoggingrebuild guard (latent, safe today) — kept as-is.